/*	--- 使用线程池 --- 


Java语言虽然内置了多线程支持，启动一个新线程非常方便。

但是，‘创建线程’需要操作系统资源（Thread线程资源，Stack栈空间等），频繁【创建和销毁】大量线程 需要 消耗大量时间。

如果可以复用一组线程： 

	┌─────┐ execute  ┌──────────────────┐
	│Task1│─────────>│ThreadPool        │
	├─────┤          │┌───────┐┌───────┐│
	│Task2│          ││Thread1││Thread2││
	├─────┤          │└───────┘└───────┘│
	│Task3│          │┌───────┐┌───────┐│
	├─────┤          ││Thread3││Thread4││
	│Task4│          │└───────┘└───────┘│
	├─────┤          └──────────────────┘
	│Task5│
	├─────┤
	│Task6│
	└─────┘
	  ...


那么我们就可以把很多小任务让一组线程来执行，而不是一个任务对应一个新线程。

这种能接收大量‘小任务’并进行‘分发处理’的就是【线程池】。

-------------------------

简单地说，【线程池】内部维护了‘若干个线程’，没有任务的时候，这些线程都处于等待状态。

如果有新任务，就分配一个空闲线程执行。

如果所有线程都处于忙碌状态，
	1. 新任务要么放入队列等待，
	2. 要么增加一个新线程进行处理。

Java标准库提供了ExecutorService接口表示线程池，它的典型用法如下：  */

// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);

// 提交任务
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.submit(task4);
executor.submit(task5);
executor.submit(task6);
executor.submit(task7);


/*
因为ExecutorService只是接口，Java标准库提供的几个常用实现class有:
	· FixedThreadPool(int num): 线程数固定的线程池
	· CachedThreadPool(): 现车跟你熟根据任务动态调整的线程池
	· SingleThreadExecutor(): 仅单线程执行的线程池

创建这些线程池的方法都被封装到Executors这个class中。

我们以Executors.newFixedThreadPool(3)为例,看看线程池的执行逻辑:  */

// thread-pool
import java.util.concurrent.*;

public class Main {
	public static void main(String[] args) {
		// 创建一个固定大小的线程池:
		ExecutorService es = Executors.newFixedThreadPool(4);

		for (int i=0; i<6; i++) {
			es.submit(new Task("" + i));
		}

		// 关闭Thread Pool
		es.shutdown();
	}
}

class Task implements Runnable {
	private final String name;

	public Task(String name) {
		this.name = name;
	}

	@Override
	public void run() {
		System.out.println("start task" + name);

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			//...
		}

		System.out.println("end task " + name);
	}
}


/*
我们观察执行结果，一次性放入6个任务，由于线程池只有固定的4个线程，因此，前4个任务会同时执行，等到有线程空闲后，才会执行后面的2个任务。


线程池在程序结束的时候要关闭。

使用 shutdown()方法关闭线程池的时候，它会等待正在执行的任务先完成，然后再关闭。

shutdownNow() 会立刻停止正在执行的任务，

awaitTermination()则会等待指定的时间让线程池关闭。

---------------------

如果我们把线程池改为 CachedThreadPool，由于这个线程池的实现会根据任务数量动态调整线程池的大小，所以6个任务可一次性全部同时执行。


如果我们想把【线程池】的大小限制在4～10个之间动态调整怎么办？我们查看Executors.newCachedThreadPool()方法的源码：  */
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
									60L, TimeUnit.SECONDS,
									new SynchronousQueue<Runable>());
}


// 因此，想创建指定动态范围的线程池，可以这么写：
int min = 4;
int max = 10;

ExecutorService es = new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());



/*----------------------------------


#	ScheduledThreadPool

还有一种任务，需要定期反复执行，例如，每秒刷新证券价格。

这种任务本身固定，需要反复执行的，可以使用ScheduledThreadPool。

放入ScheduledThreadPool的任务可以定期反复执行。


创建一个ScheduledThreadPool仍然是通过Executors类： */
ScheduledExecutorService ses = Executors.newScheduledThreadPool(4);


// 我们可以提交一次性任务，它会在指定延迟后只执行一次：
ses.scheduleAtFixedRate(new Task("fixed-rate"), 2, 3, TimeUnit.SECONDS);
	// 2s后开始执行定时任务, 每3s执行/次


// 如果任务以固定的3s为间隔执行,我们可以这样写
ses.scheduleAtFixedDelay(new Task("fixed-delay"), 2, 3, TimeUnit.SECONDS);


/*
注意FixedRate和FixedDelay的区别。


FixedRate是指任务总是以固定时间间隔触发，不管任务执行多长时间：


│░░░░   │░░░░░░ │░░░    │░░░░░  │░░░  
├───────┼───────┼───────┼───────┼────>
│<─────>│<─────>│<─────>│<─────>│
	3		3		3		3		TimeUnit.单位

-----------------------------

而FixedDelay是指，上一次任务执行完毕后，等待固定的时间间隔，再执行下一次任务：


│░░░│       │░░░░░│       │░░│       │░
└───┼───────┼─────┼───────┼──┼───────┼──>
    │<─────>│     │<─────>│  │<─────>│
		3			  3			 3		TimeUnit.单位


因此，使用ScheduledThreadPool时，我们要根据需要去选择：
	1. 执行1次  
	2. FixedRate执行  (固定 周期)
	3. FixedDelay执行。 (固定 间隔)
	

---------------------------

细心的童鞋还可以思考下面的问题：

	· 在FixedRate模式下，假设每秒触发，如果某次任务执行时间超过1秒，后续任务会不会并发执行？

	· 如果任务抛出了异常，后续任务是否继续执行？


Java标准库还提供了一个java.util.Timer类，这个类也可以定期执行任务.

但是，一个Timer会对应一个Thread.

所以，1个Timer 只能 定期执行 1个任务，多个定时任务必须启动多个Timer，而一个ScheduledThreadPool就可以调度多个定时任务.

所以，我们完全可以用ScheduledThreadPool 取代 旧的Timer。

*/




/*-------------------------------------------

#	---- 使用线程池 の 小结 ---- 

JDK提供了ExecutorService实现了线程池功能：

	· 线程池内部维护一组线程，可以高效执行大量小任务；

	· Executors提供了静态方法创建不同类型的ExecutorService；

	· 必须调用shutdown()关闭ExecutorService；

	· ScheduledThreadPool可以定期调度多个任务。


----
ExecutorService es = Executors.newSingleThreadExecutor();
	单线程池

ExecutorService es = Executors.newFixedThreadPool();
	固定的线程池

ExecutorService es = Executors.newCachedThreadPool();
	动态分配的线程池
	Cached  adj.  缓存的


- 动态分配线程池的实际实现:
	可根据(min, max, ...)指定线程池最小/最大的线程数量
ExecutorService es = new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());


ExecutorService es = new ThreadPoolExecutor(4, 8, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
	动态分配(4,8)个Thread的Thread Pool 执行器


- 时程表的执行器服务
ScheduledExecutorService ses = Executors.newScheduledThreadPool(int num);
	// 定期执行的线程池 return 定期执行的执行器服务

ses.scheduleAtFixedRate(new Runnable("task_name"), 10, 1, TimeUnit.SECONDS);
	// 10s后开始, 1s/周期

ses.scheduleAtFixedDelay(new Runnable("task_name"), 10, 5, TimeUnit.SECONDS);
	// 10s后开始, 每一次运行结束后5s间隔时间再次运行

*/


